﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.DomainServices.Client;
using System.Windows;

namespace RIABlog.Web
{
	/// <summary>
	/// Базовый класс для клиентских контекстов
	/// </summary>
	public abstract class RIABlogBaseDomainContext : DomainContext
	{
		/// <summary>
		/// Ссылка на экземпляр приложения
		/// </summary>
		public App App
		{
			get { return (App)Application.Current; }
		}

		/// <summary>
		/// ctor
		/// </summary>
		/// <param name="client"></param>
		protected RIABlogBaseDomainContext(DomainClient client) : base(client)
		{
		}

		/// <summary>
		/// Перегружаем метод, чтобы вставить градусник и обработку ошибок
		/// </summary>
		public override LoadOperation Load(EntityQuery query, LoadBehavior loadBehavior, Action<LoadOperation> callback, object userState)
		{
			// задизабливаем интерфейс
			this.App.SetIsBusy(ApplicationStrings.LoadingDataText);

			// добавляем централизованную обработку ошибок
			return base.Load
			(
				query,
				loadBehavior,
				op =>
				{
					// Инициализируем все сущности, включая дочерние.
					// Это не очень правильный подход, поскольку здесь будут проинициализированы только
					// сущности, пришедшие с сервера, а добавленные на клиенте проинициализированы
					// не будут. Но так проще, и в рамках текущей задачи нам этого достаточно.
					this.InitEntities(op.AllEntities);

					// раздизабливаем интерфейс
					this.App.ClearIsBusy();

					// вызывающий код, если захочет, может добавить свою обработку
					if (callback != null)
					{
						callback(op);
					}

					// если вызывающий код не стал обрабатывать ошибку,
					// показываем сообщение
					if ((op.HasError) && (!op.IsErrorHandled))
					{
						op.MarkErrorAsHandled();
						ErrorWindow.CreateNew(op.Error);
					}
				},
				userState
			);
		}

		/// <summary>
		/// Хэлпер для параллельного запуска нескольких load-операций.
		/// Выполняет callback, когда все запущенные операции закончатся.
		/// Аналогичным образом можно реализовать хэлперы для других 
		/// асинхронных операций.
		/// </summary>
        public void Load(EntityQuery[] queries, Action<LoadOperation[]> callback)
        {
            // задизабливаем интерфейс
            this.App.SetIsBusy(ApplicationStrings.LoadingDataText);

            var results = new List<LoadOperation>();

            // этот счетчик будет захвачен лямбдой 
            int queryCount = queries.Length;

            foreach(var query in queries)
            {
                base.Load
                (
                    query, 
                    LoadBehavior.KeepCurrent, 
                    op =>
                    {
                        // Код в этой лямбде будет выполняться в UI-потоке.
                        // Поэтому блоки не нужны и фокус с захватом локальной переменной
                        // вполне безопасен.

						// Инициализируем все сущности, включая дочерние.
						this.InitEntities(op.AllEntities);

                        if (op.HasError)
                        {
                            op.MarkErrorAsHandled();
                        }

                        // сохраняем результат выполненной операции
                        results.Add(op);

// ReSharper disable AccessToModifiedClosure
                        if((--queryCount) <= 0) // когда все операции выполнятся
// ReSharper restore AccessToModifiedClosure
                        {
                            this.App.ClearIsBusy();

                            if(callback != null)
                            {
                                // если есть обработчик от вызывающего кода - используем его
                                callback(results.ToArray());
                            }
                            else
                            {
                                // иначе выполняем общую обработку ошибок
                                var operationsWithErrors = results.Where(result => result.HasError);

                                if (operationsWithErrors.Count() > 0)
                                {
                                	ErrorWindow.CreateNew
									(
										operationsWithErrors.Aggregate(string.Empty, (s, r) => s + r.Error.Message + "\n")
									);
                                }
                            }
                        }
                    }, 
                    null
                );
            }
        }

		/// <summary>
		/// Хэлпер для параллельного запуска нескольких load-операций.
		/// </summary>
        public void Load(EntityQuery[] queries)
        {
            this.Load(queries, null);
        }

		/// <summary>
		/// Перегружаем метод, чтобы вставить градусник и обработку ошибок
		/// </summary>
		public override SubmitOperation SubmitChanges(Action<SubmitOperation> callback, object userState)
		{
			// задизабливаем интерфейс
			this.App.SetIsBusy(ApplicationStrings.SavingDataText);

			return base.SubmitChanges
			(
				op =>
				{
					this.App.ClearIsBusy();

					// вызывающий код, если захочет, может добавить свою обработку
					if (callback != null)
					{
						callback(op);
					}

					// если вызывающий код не стал обрабатывать ошибку,
					// показываем сообщение
					if ((op.HasError) && (!op.IsErrorHandled))
					{
						op.MarkErrorAsHandled();
						ErrorWindow.CreateNew(op.Error);
					}
				},
				userState
			);
		}

		/// <summary>
		/// Перегружаем метод, чтобы вставить обработку ошибок
		/// </summary>
		public override InvokeOperation<TValue> InvokeOperation<TValue>(string operationName, Type returnType, IDictionary<string, object> parameters, bool hasSideEffects, Action<InvokeOperation<TValue>> callback, object userState)
		{
			// добавляем обработчик исключений по умолчанию
			callback += op =>
			{
				// если вызывающий код не стал обрабатывать ошибку, показываем сообщение
				if ((op.HasError) && (!op.IsErrorHandled))
				{
					op.MarkErrorAsHandled();
					ErrorWindow.CreateNew(op.Error);
				}
			};

			return base.InvokeOperation(operationName, returnType, parameters, hasSideEffects, callback, userState);
		}

		/// <summary>
		/// Перегружаем метод, чтобы вставить обработку ошибок
		/// </summary>
		public override InvokeOperation InvokeOperation(string operationName, Type returnType, IDictionary<string, object> parameters, bool hasSideEffects, Action<InvokeOperation> callback, object userState)
		{
			// добавляем обработчик исключений по умолчанию
			callback += op =>
			{
				// если вызывающий код не стал обрабатывать ошибку, показываем сообщение
				if ((op.HasError) && (!op.IsErrorHandled))
				{
					op.MarkErrorAsHandled();
					ErrorWindow.CreateNew(op.Error);
				}
			};

			return base.InvokeOperation(operationName, returnType, parameters, hasSideEffects, callback, userState);
		}

		/// <summary>
		/// Инициализирует ссылку на контекст у тех сущностей, которым эта ссылка нужна
		/// </summary>
		/// <param name="entities"></param>
		private void InitEntities(IEnumerable<Entity> entities)
		{
			foreach(var entity in entities.OfType<IContextAware>())
			{
				entity.Context = this;
			}
		}
	}
}
